/**
  * @(#)Manager.java
  *
  * Abstract implementation of an application component manager to define
  * the functionality of the manager instances.
  *
  * @author Course Scheduler Team
  * 
  * @license GNU General Public License version 3 (GPLv3)
  *
  * This file is part of Course Scheduler, an open source, cross platform
  * course scheduling tool, configurable for most universities.
  *
  * Copyright (C) 2010-2012 Course Scheduler Team
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation, either version 3 of the License, or
  * (at your option) any later version.
  *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  * 
  */
package com.pollicitus.scheduler.core;

import java.io.FileNotFoundException;
import java.io.IOException;

import org.slf4j.Logger;


/**
 * Abstract implementation of an application component manager to define
 * the functionality of the manager instances.
 * 
 * @author Course Scheduler Team
 *
 */
public abstract class Manager {

	//protected property names
	public static final String MANAGER_CLASS = "manager.implementation";
	public static final String MANAGER_NAMESPACE = "manager.namespace";
	public static final String CONFIG_FILE = "manager.configuration";
	public static final String SEPARATOR = ".";
	
	//manager properties definitions
	protected static final String LOGGING_NAMESPACE = "logging.namespace";
		
	//instance variables
	protected NamespaceSettings settings;
	protected String namespace;
	protected String configFile;
	protected Logger log;
	
	/**
	 * Return the manager as configured in the properties files. This
	 * requires retrieval of the implementation specified in the primary
	 * config and loading the class definition. The implementation
	 * settings are loaded into the specified namespace, the instance is
	 * initialized and then returned. This method only performs logging
	 * for invocations that specify enableLogging = true.
	 * 
	 * @param settings the application settings manager
	 * @param managerClass the fully qualified name of the manager class
	 * @param configFile the configuration file for the retrieved instance
	 * @param settingsNamespace the settings namespace to use for the retrieved instance
	 * @param enableLogging if logging should be enabled during the manager initialization
	 * @return the primary manager for this invocation
	 * @throws FileNotFoundException if the implementation configuration file can not be found
	 * @throws IOException if there is a problem reading from the configuration file
	 * @throws InstantiationException if there is a problem instantiating the manager class
	 * @throws IllegalAccessException if there is a problem accessing the manager class
	 * @throws ClassNotFoundException if the class definition cannot be found for the manager
	 */
	protected static <T extends Manager> T getInstance(Settings settings, String managerClass, String configFile, String settingsNamespace, boolean enableLogging) throws FileNotFoundException, IOException, InstantiationException, IllegalAccessException, ClassNotFoundException{
		//load the settings into the namespace
		settings.load(settingsNamespace, configFile);
		NamespaceSettings nmSettings = new NamespaceSettings(settings, settingsNamespace);
		
		//load the logging information for the class, only if this is for non-logging managers
		Logger log = null;
		if(enableLogging){
			String loggingNamespace = nmSettings.getProperty(LOGGING_NAMESPACE);
			log = Application.logManager.getLogger(loggingNamespace);
			log.info("Starting {} logging", settingsNamespace);
		}
		
		//load the manager class, set the necessary attributes, and initialize
		@SuppressWarnings("unchecked")
		T manager = (T) Class.forName(managerClass).newInstance();
		manager.setConfig(configFile);
		manager.setNamespace(settingsNamespace);
		manager.setLog(log);
		manager.setSettings(nmSettings);
		manager.initialize(settings);
		
		//return the initialized manager
		return manager;
	}
	
	/**
	 * Return the manager as configured in the properties files. This
	 * requires retrieval of the implementation specified in the primary
	 * config and loading the class definition. The implementation
	 * settings are loaded into the specified namespace, the instance is
	 * initialized and then returned. This method performs logging for all
	 * invocations.
	 * 
	 * @param settings the application settings manager
	 * @param managerClass the fully qualified name of the manager class
	 * @param configFile the configuration file for the retrieved instance
	 * @param settingsNamespace the settings namespace to use for the retrieved instance
	 * @return the primary manager for this invocation
	 * @throws FileNotFoundException if the implementation configuration file can not be found
	 * @throws IOException if there is a problem reading from the configuration file
	 * @throws InstantiationException if there is a problem instantiating the manager class
	 * @throws IllegalAccessException if there is a problem accessing the manager class
	 * @throws ClassNotFoundException if the class definition cannot be found for the manager
	 */
	protected static <T extends Manager> T getInstance(Settings settings, String managerClass, String configFile, String settingsNamespace) throws FileNotFoundException, IOException, InstantiationException, IllegalAccessException, ClassNotFoundException{
		return Manager.<T>getInstance(settings, managerClass, configFile, settingsNamespace, true);
	}
	
	/**
	 * Initialize the Manager instance using the application settings instance.
	 * The abstract manager class uses the application settings instance
	 * to access its configuration file (specified in the configuration). This
	 * should only be called by the abstract Manager class during the application
	 * startup.
	 * 
	 * @param settings the application settings instance
	 * @throws ClassNotFoundException  in case there is some issue with initializing the logging class
	 * @throws IllegalAccessException  if the logging class cannot be properly accessed
	 * @throws InstantiationException if the logging class cannot be properly instantiated 
	 * @throws IOException  if there is a problem reading from the configuration file
	 * @throws FileNotFoundException  if the configuration file is not found
	 */
	public abstract void initialize(Settings settings) throws InstantiationException, IllegalAccessException, ClassNotFoundException, FileNotFoundException, IOException;
	
	/**
	 * Finalize the Manager during application shutdown. Gives the Manager 
	 * an opportunity to clean up any open files or configurations prior
	 * to application shutdown.
	 * 
	 * @param settings the application settings instance
	 */
	public abstract void finalize(Settings settings);
	
	/**
	 * Retrieve the current manager's settings namespace. This
	 * will be set for all manager instantiations, even though some instances
	 * may not need to know the namespace specified in the over-arching config.
	 * 
	 * @return the settings namespace for this  manager
	 */
	public String getNamespace() {
		return namespace;
	}

	/**
	 * Assign this manager's settings namespace to the specified value. This
	 * will be set for all manager instantiations, even though some instances
	 * may not need to know the namespace specified in the over-arching config.
	 * 
	 * @param namespace the new settings namespace for this manager instance
	 */
	protected void setNamespace(String namespace) {
		this.namespace = namespace;
	}

	/**
	 * Retrieve the configuration file location for this manager. This will
	 * be set for all manager instantiations, even though some instances
	 * may not need to know the location of its configuration file.
	 * 
	 * @return the configuration file location for this instance
	 */
	public String getConfig() {
		return configFile;
	}

	/**
	 * Set the configuration file location for this manager. This will
	 * be set for all manager instantiations, even though some instances
	 * may not need to know the location of its configuration file.
	 * 
	 * @param configFile the new configuration file location
	 */
	protected void setConfig(String config) {
		this.configFile = config;
	}

	/**
	 * Retrieve the implementation class of this manager. This will allow
	 * access to the specific manager implementation class.
	 * 
	 * @return the manager implementation class
	 */
	public String getClazz() {
		return this.getClass().toString();
	}

	/**
	 * Retrieve the logging instance for this manager class. This will allow 
	 * other components of the manager hierarchy access to this instance's
	 * logging component
	 * 
	 * @return the logger corresponding to this manager instance
	 */
	protected Logger getLog() {
		return log;
	}

	/**
	 * Set the manager instance's logger. This logger will be used for the lifetime
	 * of the manager. This method should only be called during the Manager.getInstance
	 * method.
	 * 
	 * @param log the logger instance which should be used by the manager
	 */
	protected void setLog(Logger log) {
		this.log = log;
	}

	/**
	 * Set the namespace settings to use for this instance. This is reduce the
	 * amount of effort needed by the end manager implementation and to reduce
	 * errors in creating the namespace settings.
	 * 
	 * @param settings the settings to use for this instance
	 */
	protected void setSettings(NamespaceSettings settings) {
		this.settings = settings;
	}
}
